<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- 迭代器 -->
    <script>
        // 手写迭代器生成函数
        function makeIterator(arr) {
            let nextIndex = 0;
            let length = arr.length;
            return {
                next() {
                    return (nextIndex < length) ?
                        { value: arr[nextIndex++] } :
                        { done: true }
                }
            }
        }
        let iter = makeIterator(['a', 'b', 'c']);
        console.log(iter.next(), iter.next(), iter.next(), iter.next());

        // es6规定, 数据结构中具有[Symbol.iterator]属性的, 都是可遍历的
        // 对象中不具有, 一个对象如果要具备可被for...of循环调用的 Iterator 接口，就必须在Symbol.iterator的属性上部署遍历器生成方法
        let obj = { a: 'aa', b: 'bb', c: 'cc' };
        // 把部署的方法封装在Object原型中
        Object.prototype[Symbol.iterator] = function () {
            let keys = Object.keys(this); // 提取所有keys
            let index = 0;
            const length = keys.length;
            return { // 返回迭代器对象
                next: () => { // 箭头函数this绑定实例对象
                    if (index < length) {
                        let key = keys[index]; // 当前属性名
                        index++;
                        return { value: { [key]: this[key] }, done: false };
                    }
                    else
                        return { value: undefined, done: true };
                },
                return: () => {
                    // return 用于提前终止迭代器
                    console.log('I am return');
                }
            }
        }
        for (let key of obj) {
            console.log(key);
        }
        // 不借助Iterator接口, 使用Object.keys方法将对象的键名生成一个数组，然后遍历这个数组
        for (let key of Object.keys(obj)) {
            console.log(`${key}: ${obj[key]}`);
        }

        console.log('--------------------------------------');
    </script>

    <!-- 生成器 -->
    <script>
        /* 
            Generator函数 - 遍历器生成函数
            特征一 - function关键字与函数名之间有*号
            特征二 - 函数体内部使用yield表达式
        */

        /* 定义方式 */
        // 1 - 函数声明
        {
            function* myGenerator1() { };
            // 2 - 函数表达式
            let myGenerator2 = function* () { };
            // 3 - 作为对象方法
            let mg3 = {
                * myGenerator3() { },
                myGenerator4: function* () { }
            }
        }

        /* 基本使用方法 */
        function* helloWorldGenerator() {
            yield 'hello';
            yield 'world';
            return 'end';
        }
        const hwg = helloWorldGenerator();
        console.log(hwg.next(), hwg.next(), hwg.next(), hwg.next());
        // 第四次调用时, Generator函数运行完毕, 默认返回值value: undefined

        /* next 传参问题 */
        function* generatorFn1(initial) {
            console.log(initial);
            console.log(yield);
            console.log(yield);
        }
        let generatorObj = generatorFn1('aa');
        generatorObj.next('bb'); // aa - 此次传入的值不被使用, 因为这一次调用是为了开始执行生成器函数
        generatorObj.next('cc'); // cc
        generatorObj.next('dd'); // dd

        function* generatorFn2(x) {
            var y = 2 * (yield (x + 1)); // yield1
            var z = yield (y / 3); // yield2
            return (x + y + z);
        }
        let a = generatorFn2(5);
        console.log(a.next()); // 6
        console.log(a.next(12)); // 8, 12上一次暂停的yield1表达式的值
        console.log(a.next(13)); // 42 - x 5 y 24 z 13
        // 13为上一次暂停的yield2表达式的值, yield1表达式的值为12, 传入参数为5


        /* 生成器部署迭代器 */
        function objGenerator(obj) {
            let key = Object.keys(obj);
            let length = key.length;
            obj[Symbol.iterator] = function* () {
                let index = 0;
                while (length--) {
                    yield { [key[index]]: this[key[index]] };
                    index++;
                }
            }
            return obj;
        }
        let obj11 = objGenerator({ a: 1, b: 2, c: 3 });
        console.log(...obj11);

        /* 生成器实现斐波那契数列 */
        function* fibonacci() {
            // (0) 1 1 2 3 5 8 ... 只计算至10位
            let [prev, curr] = [0, 1];
            for (let i = 0; i < 10; i++) {
                yield curr;
                [prev, curr] = [curr, prev + curr];
            }

        }
        const fin = fibonacci();
        console.log(...fin);

        /* throw() - 在暂停时将一个提供的错误注入到生成器对象中 */
        // 错误未被处理, 生成器关闭
        function* generatorFn3() {
            for (const x of [1, 2, 3]) {
                yield x;
            }
        }
        const g3 = generatorFn3();
        console.log(g3, g3.next()); // suspend, 生成器暂停
        try {
            g3.throw('foo');
        } catch (error) {
            console.log(error);
        }
        console.log(g3, g3.next()); // closed, 生成器关闭
        // 错误在生成器函数内部处理, 错误处理跳过对应yield, 生成器不关闭, 恢复执行
        function* generatorFn4() {
            for (const x of [1, 2, 3]) {
                try {
                    yield x;
                } catch (error) {
                }
            }
        }
        const g4 = generatorFn4();
        console.log(g4.next()); // 1
        g4.throw('foo'); // 错误被yield抛出, 生成器不再产出2
        console.log(g4.next()); // 3

        /* return() - 使生成器强行关闭, 参数为终止迭代器对象的值 */
        function* generatorFn5() {
            for (const x of [1, 2, 3]) {
                yield x;
            }
        }
        const g5 = generatorFn5();
        console.log(g5.next()); // 1
        console.log(g5.return('end')); // end
        console.log(g5.next()); // undefined

        /* yield* 表达式 - 迭代生成器*/
        function* generatorFn6() {
            yield* [1, 2, 3]; // = for..of yield 遍历 [1, 2, 3]
            // yield [1, 2, 3]; // 返回一整个数组
        }
        const g6 = generatorFn6();
        console.log(g6.next()); // 1
        console.log(g6.next()); // 2
        console.log(g6.next()); // 3

        function* generatorFn7() {
            yield 2;
            yield 3;
            return "foo";
        }
        function* generatorFn8() {
            yield 1;
            var v = yield* generatorFn7();
            console.log("v: " + v);
            yield 4;
        }
        var it = generatorFn8();
        console.log(it.next()); // 1
        console.log(it.next()); // 2
        console.log(it.next()); // 3
        console.log(it.next()); // v: foo, 4, 此时接收到fn7的返回值
        console.log(it.next()); // undefined

        function* genFuncWithReturn() {
            yield 'a';
            yield 'b';
            return 'result';
        }
        function* logReturned(genObj) {
            let result = yield* genObj;
            console.log(result);
        }
        const l = logReturned(genFuncWithReturn());
        const g = genFuncWithReturn();
        console.log(...l); // a b result, .log(result) 输出返回值result
        console.log(...g);

        /* yield* 简单递归 */
        function* nTime(n) {
            if (n > 0) {
                yield* nTime(n - 1);
                yield n - 1;
            }
        }
        const nt = nTime(3);
        console.log(...nt);

        /* yield* 运用递归遍历完全二叉树 */
        // 下面是二叉树的构造函数，三个参数分别是左树、当前节点和右树
        function Tree(left, label, right) {
            this.left = left;
            this.label = label;
            this.right = right;
        }
        // 下面是中序（inorder）遍历函数。
        // 由于返回的是一个遍历器，所以要用generator函数。
        // 函数体内采用递归算法，所以左树和右树要用yield*遍历
        function* inorder(t) {
            if (t) {
                yield* inorder(t.left);
                yield t.label;
                yield* inorder(t.right);
            }
        }
        // 下面生成二叉树
        function make(array) {
            // 判断是否为叶节点
            if (array.length == 1) return new Tree(null, array[0], null);
            return new Tree(make(array[0]), array[1], make(array[2]));
        }
        let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
        // 遍历二叉树
        let result = [];
        for (let node of inorder(tree)) {
            result.push(node);
        }
        console.log(tree, result);

        /* 正确获取生成器中的this & 使生成器可以使用new */
        function* gg1(){ this.a = 1; }
        let ggg1 = gg1();
        console.log(ggg1.a); // undefined, this指向whindow, 不是指向实例对象
        try {
            new gg1();
        } catch (error) {
            console.log('gg1 is not a constructor'); // 无法使用new操作符
        }
        // 解决方案
        function* gg2(){ this.a = 1; yield this.b = 2}
        function F2(){ return gg2.call(gg2.prototype); }
        // F2为构造方法, 可以解决使用new问题
        // call改变this指向, 指向gg2的原型避免遍历器对象和生成对象实例不同的问题
        let f2 = new F2();
        console.log(f2.next());
        console.log(f2.next());
    </script>
</body>

</html>